package com.example.springsecurity.common.utils;

import com.example.springsecurity.common.config.SystemUserLoginDetail;
import com.example.springsecurity.common.enums.GlobalExceptionEnum;
import com.example.springsecurity.common.exception.BizException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @Description：JwtToken生成的工具类
 * @Author：fengcheng
 * @Date：2025/1/17 11:06
 */
@Slf4j
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtTokenUtil {

    private static final String CLAIM_KEY_MOBILE = "mobile";

    private static final String CLAIM_KEY_CREATED = "created";
    // JWT加解密使用的密钥
    @Value("jwt.secret")
    private String secret;
    // #JWT的超期限时间（单位是秒） 7天过期
    @Value("jwt.expiration")
    private String expiration;
    // JWT存储的请求头
    @Value("jwt.tokenHeader")
    private String tokenHeader;
    // JWT负载中拿到开头
    @Value("jwt.tokenHead")
    private String tokenHead;

    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    /**
     * @Description：根据负责生成JWT的token
     * @Author：fengcheng
     * @Date：2025/1/17 11:07
     * @Param：claims
     * @ParamsType：[java.util.Map<java.lang.String,java.lang.Object>]
     * @Return：java.lang.String
     */
    private String generateToken(Map<String, Object> claims) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        return Jwts.builder()
                .setId(getUUID())       //唯一的ID
                .setClaims(claims)      // 主题  可以是JSON数据
                .setIssuer("zdxlz")     // 签发者
                .setIssuedAt(now)       // 签发时间
                .signWith(SignatureAlgorithm.HS512, secret) //使用HS512对称加密算法签名, 第二个参数为秘钥
                .setExpiration(generateExpirationDate()).compact();
    }

    /**
     * @Description：从token中获取JWT中的负载
     * @Author：fengcheng
     * @Date：2025/1/17 11:02
     * @Param：token
     * @ParamsType：[java.lang.String]
     * @Return：io.jsonwebtoken.Claims
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            log.error("JWT验证【token】失败:{}", token, e);
            throw new BizException("JWT验证【token】失败");
        }
        return claims;
    }

    /**
     * @Description：生成token的过期时间
     * @Author：fengcheng
     * @Date：2025/1/17 11:03
     * @ParamsType：[]
     * @Return：java.util.Date
     */
    private Date generateExpirationDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, Integer.parseInt(expiration)); // 添加指定秒数
        return calendar.getTime();
    }

    /**
     * @Description：从token中获取登录账号
     * @Author：fengcheng
     * @Date：2025/1/17 11:07
     * @Param：token
     * @ParamsType：[java.lang.String]
     * @Return：java.lang.String
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.get(CLAIM_KEY_MOBILE, String.class);
        } catch (Exception e) {
            throw new BizException(GlobalExceptionEnum.ERROR_TOKEN_EXPIRED.getCode(), e.getMessage());
        }
        return username;
    }

    /**
     * @Description：验证token是否还有效
     * @Author：fengcheng
     * @Date：2025/1/17 11:08
     * @Param：token
     * @Param：userDetails
     * @ParamsType：[java.lang.String, org.springframework.security.core.userdetails.UserDetails]
     * @Return：boolean
     */
    public boolean validateToken(String token, UserDetails userDetails) {
        String userPhone = ((SystemUserLoginDetail) userDetails).getSystemUser().getMobile();
        String username = getUserNameFromToken(token);
        return username.equals(userPhone) && isTokenExpired(token);
    }

    /**
     * @Description：判断token是否已经失效
     * @Author：fengcheng
     * @Date：2025/1/17 11:08
     * @Param：token
     * @ParamsType：[java.lang.String]
     * @Return：boolean
     */
    private boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return !expiredDate.before(new Date());
    }

    /**
     * @Description：从token中获取过期时间
     * @Author：fengcheng
     * @Date：2025/1/17 11:08
     * @Param：token
     * @ParamsType：[java.lang.String]
     * @Return：java.util.Date
     */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * @Description：根据用户信息生成token
     * @Author：fengcheng
     * @Date：2025/1/17 11:09
     * @Param：mobile
     * @ParamsType：[java.lang.String]
     * @Return：java.lang.String
     */
    public String generateToken(String mobile) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_MOBILE, mobile);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * @Description：判断token是否可以被刷新
     * @Author：fengcheng
     * @Date：2025/1/17 11:09
     * @Param：token
     * @ParamsType：[java.lang.String]
     * @Return：boolean
     */
    public boolean canRefresh(String token) {
        return isTokenExpired(token);
    }

    /**
     * @Description：刷新token
     * @Author：fengcheng
     * @Date：2025/1/17 11:09
     * @Param：token
     * @ParamsType：[java.lang.String]
     * @Return：java.lang.String
     */
    public String refreshToken(String token) {
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /**
     * @Description：从请求头中获取token
     * @Author：fengcheng
     * @Date：2025/1/17 11:10
     * @Param：request
     * @ParamsType：[jakarta.servlet.http.HttpServletRequest]
     * @Return：java.lang.String
     */
    public String getToken(HttpServletRequest request) {
        final String requestHeader = request.getHeader(tokenHeader);
        if (requestHeader != null && requestHeader.startsWith(tokenHead)) {
            return requestHeader.substring(7);
        }
        return null;
    }

}
